"""
PM 应用程序主程序

date: 2023/8/20
author: SiHeng Tang
file: app.py
copyright(c) DFSA Software Developers
此程序不能提供任何担保 WITHOUT WARRANTY OF ANY KIND
"""
import copy
import importlib
import os
import sys
import time

from libpm import core, service


class Argument(core.PArgument):
    """
    命令行指令解析器：
    e.g. pm <DO_TASK> <EXTENSION_NAME> [switches] <TARGET> [optional arguments] ...

    Argument 建议使用统一风格的指令来进行项目维护，主要借鉴的是 apt 的命令风格。
    Argument 为了向下兼容 Argument 提供两种参数格式

    旧式的 PM 命令行参数解析:
        该参数用法将于 0.1 版本弃用
        对于 EXT_<NUM> 用法已经弃用
        DO_TASK:
            执行的操作，例如 CREATE，UPDATE，DELETE，CONFIG，
            在加载完扩展之后将调用扩展包 do_<DO_TASK> 对应的函数，
            使用 do_ALL 在没有对应方法的情况下接受指令。
        EXTENSION_NAME:
            扩展包名，同时也是 PM 找到扩展的唯一方式。
        TARGET:
            对象标识，由相应的扩展进行进一步解释。

    新增的 Argument 标准参数格式:
        self['task_name']:
            同旧式的参数 DO_TASK
        self['extension_name']:
            同旧式的参数 EXTENSION_NAME
        self['<arg>[:]']:
            与 Linux 风格相同的带参数或者开关命令行参数
    """

    def __init__(self):
        # Argument 同时持有原始参数和解析后的参数，只有请求时才进行参数解析
        self.raw_argv = copy.deepcopy(sys.argv[1:])
        self.argv_dict = {}

        # 尝试绑定扩展和任务名
        if len(self.raw_argv) >= 3:
            if not self.raw_argv[0].startswith('-'):
                self.argv_dict['task_name'] = self.raw_argv[0]
            if not self.raw_argv[1].startswith('-'):
                self.argv_dict['extension_name'] = self.raw_argv[1]
            if not self.raw_argv[2].startswith('-'):
                self.argv_dict['target'] = self.raw_argv[2]
        else:
            # TODO 非正常的参数情况
            pass

    def __getitem__(self, item):
        if (type(item) != str) or not bool(item):
            raise core.PError(item, info='Required type not supported.')

        else:
            # 先查询已解析的参数
            if item in self.argv_dict.keys():
                return self.argv_dict[item]

            else:
                if item[-1] == ':':
                    # 有参数值的参数
                    arg_name = item[:-1]

                    # 短参数，需要考虑合并
                    if len(arg_name) == 1:
                        for i, raw_arg in enumerate(self.raw_argv):
                            if raw_arg.startswith('-') and (not raw_arg.startswith('--')):
                                for option in raw_arg[1:]:
                                    if option == item[0]:
                                        self.argv_dict[item] = self.get_next_v(self.raw_argv, i)
                                        return self.argv_dict[item]
                    # 长参数，整体匹配，支持等号传参
                    else:
                        for i, raw_arg in enumerate(self.raw_argv):
                            if '=' in raw_arg:
                                raw_arg, value = raw_arg.split('=', 1)
                                if raw_arg.startswith('--'):
                                    if arg_name == raw_arg[2:]:
                                        self.argv_dict[item] = value
                                        return self.argv_dict[item]
                            else:
                                if raw_arg.startswith('--'):
                                    if arg_name == raw_arg[2:]:
                                        self.argv_dict[item] = self.get_next_v(self.raw_argv, i)
                                        return self.argv_dict[item]

                else:
                    # 开关
                    # 短参数，需要考虑合并
                    if len(item) == 1:
                        for raw_arg in self.raw_argv:
                            if raw_arg.startswith('-') and (not raw_arg.startswith('--')):
                                self.argv_dict[item] = item in raw_arg
                                return self.argv_dict[item]
                    # 长参数，整体匹配
                    else:
                        self.argv_dict[item] = ('--' + item) in self.raw_argv
                        return self.argv_dict[item]

    def __getattr__(self, item):
        sys.stderr.write(f'Directly use {item} is not recommended and will be removed in 0.1 ver\n')
        match item:
            case 'DO_TASK':
                return self['task_name']
            case 'EXTENSION_NAME':
                return self['extension_name']
            case 'TARGET':
                return self['target']
            case _:
                raise core.PError(item, info='Target not in argument list.')

    @staticmethod
    def get_next_v(l: list[str], i: int):
        for raw_arg in l[i:]:
            if not raw_arg.startswith('-'):
                return raw_arg

        return None


class Application(core.PApplication):
    LOG_TEMPLATE = {
        core.LOG_LV_WARN: '(warn)\033[33m[%(time)s] PM: %(content)s\033[0m\n',
        core.LOG_LV_INFO: '(info)\033[36m[%(time)s] PM: %(content)s\033[0m\n',
        core.LOG_LV_ERR: '(err)\033[31m[%(time)s] PM: %(content)s\033[0m\n',
        core.LOG_LV_DEBUG: '(debug)[%(time)s] PM: %(content)s\n'
    }

    def __init__(self, *args):
        super().__init__()

        self.argv = Argument()
        self._top_dir = os.environ["HOME"] + '/.local/pm'

        self._factory = service.SuperFactory(self)

    def run(self) -> None:
        try:
            pak = PakLoader(self.argv['extension_name'], self.argv['task_name'], self)
        except core.PError as err:
            self.log(str(err), level=core.LOG_LV_ERR)
            exit(core.EXIT_USAGE_ERR)

        try:
            pak.run()
        except core.PExtensionError as err:
            # 扩展设计或者使用问题，对框架不致命
            self.log(str(err), core.LOG_LV_ERR)
        except core.PError as err:
            # 与框架有关的异常，需要考虑上报
            # TODO 更好的异常处理机制
            self.log(str(err), core.LOG_LV_ERR)

    def get_top_dir(self) -> str:
        return self._top_dir

    def msg(self, *args, **kwargs):
        print(*args, **kwargs)

    def log(self, string: str, level: int):
        str_time = time.strftime('%H:%M:%S', time.localtime())
        content = self.LOG_TEMPLATE[level] % {'time': str_time, 'content': string}

        if (level <= core.LOG_LV_DEBUG) and ('--debug' in sys.argv):
            # 调试等级及以下
            sys.stdout.write(content)
        elif core.LOG_LV_DEBUG < level < core.LOG_LV_ERR:
            # 常规日志
            sys.stdout.write(content)
        else:
            # 严重日志
            sys.stderr.write(content)

    def get_service(self, product: str):
        return self._factory.get_product(product)

    def dialog(self, *args, **kwargs):
        # TODO dialog 方法
        pass

    def progress(self, percent: float):
        # TODO progress 方法
        pass

    # def image(self):
    #     pass


class PakLoader(core.PObject):
    def __init__(self, name: str, method: str, app: Application):
        path = f'extension.{name}.main'
        module = importlib.import_module(path)
        method = 'do_' + method

        try:
            ext_inst = module.Extension(app)
            self.func = getattr(ext_inst, method)
        except AttributeError:
            raise core.PError(info='Method not in module')

    def run(self):
        self.func()
